#include "OrthographicCamera.h"

using namespace OBR;

OrthographicCamera::OrthographicCamera() : Camera("shaders/Camera/OrthographicCamera.cu")
{
    this->program_desc.raygen.entryFunctionName = "__raygen__orthographic_camera";
    this->program_desc.kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN;
    this->program_desc.flags = OPTIX_GEOMETRY_FLAG_NONE;
}

OrthographicCamera::OrthographicCamera(float3 _orig, float3 _lookat, float3 _up, // camera facing
                                       float _znear, float _zfar,                // z - range
                                       float _width, float _height)
    : Camera("shaders/Camera/OrthographicCamera.cu")
{
    if (_width > 0 && _height > 0)
    {
        this->orig = _orig;
        this->lookat = _lookat;
        this->up = _up;
        this->znear = _znear;
        this->zfar = _zfar;
        this->width = _width;
        this->height = _height;
    }
    else
    {
        std::cerr << "You should specify [width, height > 0]" << std::endl;
        throw std::runtime_error("You should specify [width, height > 0]");
    }

    this->program_desc.raygen.entryFunctionName = "__raygen__orthographic_camera";
}

OrthographicCamera::~OrthographicCamera()
{
}

void OrthographicCamera::uploadSbtData()
{
    OrthographicCameraData data;
    data.U = this->U;
    data.V = this->V;
    data.dir = this->dir;
    data.orig = this->orig;
    data.znear = this->znear;
    data.zfar = this->zfar;

    this->sbt_data.resize(sizeof(OrthographicCameraData));
    this->sbt_data.upload(&data, sizeof(OrthographicCameraData));
}

void OrthographicCamera::calculateParams()
{
    // calculate UV
    this->cam_front = normalize(this->lookat - this->orig);
    this->cam_right = normalize(cross(this->cam_front, this->up));
    this->cam_up = normalize(cross(this->cam_right, this->cam_front));
    this->U = this->cam_right;
    this->V = -this->cam_up;
    this->U *= this->width;
    this->V *= this->height;
    this->dir = this->cam_front;
}

void OrthographicCamera::_buildFromConfig(const Json::Value &config)
{
    // defalut values
    this->medium_idx = config.isMember("medium") ? config["medium"].asUInt() : -1;
    this->up = make_float3(config["up"][0].asFloat(), config["up"][1].asFloat(),
                           config["up"][2].asFloat());
    this->orig = make_float3(config["origin"][0].asFloat(), config["origin"][1].asFloat(),
                             config["origin"][2].asFloat());
    this->lookat = make_float3(config["lookat"][0].asFloat(), config["lookat"][1].asFloat(),
                               config["lookat"][2].asFloat());
    this->znear = config["znear"].asFloat();
    this->zfar = config["zfar"].asFloat();
    this->width = config["width"].asFloat();
    this->height = config["height"].asFloat();
    this->calculateParams();
}

void OrthographicCamera::rotate(float2 deg)
{
    float r = length(this->orig - this->lookat);
    float x = r * cosf(deg.y) * cosf(deg.x);
    float y = r * cosf(deg.y) * sinf(deg.x);
    float z = r * sinf(deg.y);
    this->orig = this->lookat - x * this->cam_front + y * this->cam_right + z * this->cam_up;
    this->calculateParams();
    this->uploadSbtData();
}

void OrthographicCamera::translate(float2 dir)
{
    this->orig += dir.x * this->cam_right + dir.y * this->cam_up;
    this->lookat += dir.x * this->cam_right + dir.y * this->cam_up;
    this->calculateParams();
    this->uploadSbtData();
}

void OrthographicCamera::zoom(float dist)
{
    this->orig += normalize(this->lookat - this->orig) * dist;
    this->calculateParams();
    this->uploadSbtData();
}

OptixProgramGroupOptions &OrthographicCamera::getProgramGroupOptions()
{
    return this->program_opt;
}

OptixProgramGroupDesc &OrthographicCamera::getProgramGroupDesc()
{
    this->program_desc.raygen.module = this->cuda_module->optix_module;
    return this->program_desc;
}